
<pre>
  <script>
    //  JavaScript 词法和语法的代码分析
    // 利用正则文法 表示产生式
  
    let xregexp = { // 正则表达式里，() 能分组也能捕获 只想分组不想捕获，需要加上?:
      InputElement: "<Whitespace>|<LineTerminator>|<Comments>|<Token>",
      Whitespace: / /,
      LineTerminator: /\n/,
      Comments: /\/\*([^*]|\*[^\/])*\*\/|\/\/[^\n]*/,
      Token: "<Literal>|<Keywords>|<Identifer>|<Punctuator>",
      Literal: "<NumbericLiteral>|<BooleanLiteral>|<StringLiteral>|<NullLiteral>",
      NumbericLiteral: /(?:[1-9][0-9]*|0)(?:\.[0-9]*)?|\.[0-9]+/,
      BooleanLiteral: /true|false/,
      StringLiteral: /\"(?:[^"\n]|\\[\s\S])*\"|\'(?:[^'\n]|\\[\s\S])*\'/,
      NullLiteral: /null/,
      Keywords: /if|else|for|function|let|var/,
      Identifer: /[a-zA-Z_$][a-zA-Z0-9_$]*/,
      Punctuator: /\+|\,|\?|\.|\:|\{|\}|\(|\)|\[|\]|\=|\<|\>|\+\+|\=\=|\=\>|\*|;/
    }

    class XRegExp { // 封装一个类(词法分析器) 重写exec、lastIndex 重新进行编译，并分成有序列的部分
      constructor(source, flag, root = 'root') {
        this.table = new Map() // 使用 Map 类型进行构造
        this.regexp = new RegExp(this.compileRegExp(source, root, 0).source, flag)
        console.log(this.regexp)
        console.log(this.table)
      }

      compileRegExp(source, name, start) { // start 序号
        if (source[name] instanceof RegExp) // 区分叶子节点
          return {
            source: source[name].source,
            length: 0
          }
        
        let length = 0
        
        // 匹配每个尖括号里面的内容 要算在对应的数组里的位置，需要知道length 
        let regexp = source[name].replace(/\<([^>]+)\>/g, (str, $1) => {
          this.table.set(start + length, $1)

          ++length

          let r = this.compileRegExp(source, $1, start + length)

          length += r.length

          return "(" + r.source + ")"
        })

        return {
          source: regexp,
          length
        }
      }

      exec(string) {
        let r = this.regexp.exec(string)
        // console.log.log(r)
        for(let i = 1; i < r.length; i++) { // 循环匹配对应的值 从1开始
          if (r[i] !== void 0) {
            // console.log(this.table.get(i - 1))
            r[this.table.get(i - 1)] = r[i]
          }
        }
        return r
      }

      get lastIndex() {
        return this.regexp.lastIndex
      }

      set lastIndex(newVal) {
        return this.regexp.lastIndex = newVal
      }
    }

    function scan(str) {
      // let regexp = / |\n|\/\*([^*]|\*[^\/])*\*\/|\/\/[^\n]*|[1-9][0-9]*|0/g
      // let regexp = new RegExp(compileRegExp(xregexp, "InputElement"), "g")
      let regexp = new XRegExp(xregexp, "g", "InputElement")

      while(regexp.lastIndex < str.length) {
        let r = regexp.exec(str)
        // console.log(r)
        document.write(r[0])
        if (!r[0].length) break;
      }
    }
    
    scan(`
      for(let i = 0; i < 3; i++) {
        for(let j = 0; j < 3; j++) {
          let cell = document.createElement("div");
          cell.classList.add("cell");
          cell.innerText = pattern[i * 3 + j] == 2 ? "❌" :
            pattern[i * 3 + j] == 1 ? "⭕" : "";
          cell.addEventListener("click", () => userMove(j, i));
          board.appendChild(cell);
        }
        board.appendChild(document.createElement("br"));
      }
    `)
    
  </script>
</pre>